---
id: dbcontext
title: 9.1 数据库上下文
sidebar_label: 9.1 数据库上下文
---

## 9.1.1 数据库上下文

简单来说，数据库上下文是负责和数据库交互的对象，提供程序对数据库存取提供了大量的方法。

在 `Fur` 框架中，默认集成了微软亲儿子：`EntityFramework Core` ，也就是通常数据库上下文指的是 `DbContext` 类或它的实现类。

## 9.1.2 `AppDbContext`

在我们实际项目开发过程中，使用 `EFCore` 提供的 `DbContext` 操作对象操作数据库有些繁琐和复杂，且默认不具备读写分离、多库等操作功能。

所以，`Fur` 框架提供了 `AppDbContext<TDbContext, TDbContextLocator>` 数据库上下文，该上下文继承自 `DbContext`。

:::note 特别说明
后续章节，皆采用 `EFCore` 代替 `EntityFramework Core`。
:::

## 9.1.3 `AppDbContext` 和 `DbContext` 区别

- `AppDbContext` 继承自 `DbContext`，具备 `DbContext` 所有功能。
- `AppDbContext` 支持多数据库操作泛型版本，如：`AppDbContext<TDbContext, TDbContextLocator>`
- `AppDbContext` 自动配置实体信息，无需在 `OnModelCreating` 中配置
- `AppDbContext` 支持内置多租户支持
- `AppDbContext` 支持全局模型配置拦截器
- `AppDbContext` 支持数据提交更改多个事件
- `AppDbContext` 提供更加强大的模型操作能力，如 `Sql` 操作，读写分离等
- `AppDbContext` 能够得到 `Fur` 框架更多的功能支持

## 9.1.4 如何定义数据库上下文

在 `Fur` 框架中了，提供了两种 `AppDbContext` 定义方式：

- `AppDbContext<TDbContext>` 操作默认数据库
- `AppDbContext<TDbContext, TDbContextLocator>` 操作 N 个数据库

其中 `AppDbContext<TDbContext>` 默认继承自 `AppDbContext<TDbContext, TDbContextLocator>`。

下面是数据库上下文创建的多个例子：

### 9.1.4.1 创建默认数据库上下文

```cs {1,6,12}
using Fur.DatabaseAccessor;
using Microsoft.EntityFrameworkCore;

namespace Fur.EntityFramework.Core
{
    [AppDbContext("连接字符串或appsetting.json 键")]
    public class FurDbContext : AppDbContext<FurDbContext>  // 继承 AppDbContext<> 类
    {
        /// <summary>
        /// 继承父类构造函数
        /// </summary>
        /// <param name="options"></param>
        public FurDbContext(DbContextOptions<FurDbContext> options) : base(options)
        {
        }
    }
}
```

### 9.1.4.2 创建其他数据库上下文

```cs {1,6,12}
using Fur.DatabaseAccessor;
using Microsoft.EntityFrameworkCore;

namespace Fur.EntityFramework.Core
{
    [AppDbContext("连接字符串或appsetting.json 键")]
    public class FurOtherDbContext : AppDbContext<FurOtherDbContext, FurOtherDbContextLocator>  // 继承 AppDbContext<> 类
    {
        /// <summary>
        /// 继承父类构造函数
        /// </summary>
        /// <param name="options"></param>
        public FurOtherDbContext(DbContextOptions<FurOtherDbContext> options) : base(options)
        {
        }
    }
}
```

:::important 特别注意
所有数据库上下文都应该在 `Fur.EntityFramework.Core` 项目中创建。关于 `TDbContextLocator` 将在下一章节 《[9.2 数据库上下文定位器](dbcontext-locator)》阐述。
:::

## 9.1.5 配置连接字符串

`Fur` 框架提供多种数据库连接字符串配置方式：

- 在注册数据库服务时配置：`AddSqlServerPool<TDbContext>("连接字符串")` 方式
- 使用 `[AppDbContext("连接字符串/Key")]` 特性方式（只在 `AppDbContext 实现类有效`）**推荐**
- 通过重写 `OnConfiguring(DbContextOptionsBuilder optionsBuilder)` 配置

### 9.1.5.1 在注册数据库服务时配置

```cs {12-19} title="Fur.EntityFramework.Core\FurEntityFrameworkCoreStartup.cs"
using Fur.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

namespace Fur.EntityFramework.Core
{
    [AppStartup(600)]
    public sealed class FurEntityFrameworkCoreStartup : AppStartup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // 配置数据库上下文，支持N个数据库
            services.AddDatabaseAccessor(options =>
            {
                // 配置默认数据库
                options.AddDbPool<FurDbContext>(DbProvider.SqlServer, "连接字符串");

                // 配置多个数据库，多个数据库必须指定数据库上下文定位器
                options.AddDbPool<SqliteDbContext, SqliteDbContextLocaotr>(DbProvider.Sqlite, "连接字符串");
            });
        }
    }
}
```

### 9.1.5.2 `[AppDbContext]` 方式配置

```cs {1,6}
using Fur.DatabaseAccessor;
using Microsoft.EntityFrameworkCore;

namespace Fur.EntityFramework.Core
{
    [AppDbContext("DbConnectionString")]   // 支持 `appsetting.json` 名或 连接字符串
    public class FurDbContext : AppDbContext<FurDbContext>
    {
        /// <summary>
        /// 继承父类构造函数
        /// </summary>
        /// <param name="options"></param>
        public FurDbContext(DbContextOptions<FurDbContext> options) : base(options)
        {
        }
    }
}
```

:::tip 小提示

`Fur` 推荐使用此方式配置数据库连接字符串。

:::

**`[AppDbContext]`** 内置属性：

- `ConnectionString`：数据库连接字符串，或配置文件中的路径（支持自定义配置查找），或 `appsetting.json` 的 `ConnectionStrings` 配置字符串
- `TablePrefix`：当前数据库上下文表统一前缀
- `TableSuffix`：当前数据库上下文表统一后缀

### 9.1.5.3 `OnConfiguring` 方式配置

```cs {16-20}
using Fur.DatabaseAccessor;
using Microsoft.EntityFrameworkCore;

namespace Fur.EntityFramework.Core
{
    public class FurDbContext : AppDbContext<FurDbContext>
    {
        /// <summary>
        /// 继承父类构造函数
        /// </summary>
        /// <param name="options"></param>
        public FurDbContext(DbContextOptions<FurDbContext> options) : base(options)
        {
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            base.OnConfiguring(optionsBuilder);

            optionsBuilder.UseSqlServer("数据库连接字符串");
        }
    }
}
```

:::important 特别注意

这三种方式可以同时使用，但是有优先级：`[AppDbContext]` -> `在注册数据库服务时配置` -> `OnConfiguring`（低到高）

也就是 `OnConfiguring` 配置会覆盖 `在注册数据库服务时配置` 配置，`在注册数据库服务时配置` 配置会覆盖 `[AppDbContext]` 配置所配置的连接字符串。

:::

## 9.1.6 数据库上下文定义位置

:::important 特别注意

在 `Fur` 框架中，数据库上下文需定义在 `Fur.EntityFramework.Core` 中，且每一个数据库上下文都必须拥有唯一的 `DbContextLocator` 定位器

:::

## 9.1.7 数据库上下文注册

数据库上下文配置好数据库连接字符串后，需要注册该数据库上下文，并指定数据库类型，如：

```cs {11-13} title="Fur\framework\Fur.EntityFramework.Core\FurEntityFrameworkCoreStartup.cs"
using Fur.DatabaseAccessor;
using Microsoft.Extensions.DependencyInjection;

namespace Fur.EntityFramework.Core
{
    [AppStartup(600)]
    public sealed class FurEntityFrameworkCoreStartup : AppStartup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDatabaseAccessor(options =>
            {
                options.AddDbPool<FurDbContext>(DbProvider.Sqlite);
            });
        }
    }
}
```

如果有多个数据库操作，**那么从第二个起，就需要绑定数据库上下文定位器**，如：

```cs
options.AddDbPool<FurDbContext>(DbProvider.Sqlite); // 第一个数据库

options.AddDbPool<SecondDbContext, SecondDbContextDbContextLocator>(DbProvider.SqlServer);  // 第二个数据库

options.AddDbPool<ThirdDbContext, ThirdDbContextDbContextLocator>(DbProvider.SqlServer);  // 第三个数据库
```

## 9.1.8 动态数据库上下文对象

在 `Fur` 框架中，数据库上下文是定义在 `Fur.EntityFramework.Core` 项目层，并且该层不被 `Fur.Application` 和 `Fur.Core` 等层引用。

所以就不能直接在 `Fur.Application` 项目层直接使用 `Fur.EntityFramework.Core` 定义的数据库上下文。

`Fur` 为了解决这个问题，提供了两种方式处理：

- `respository.DbContext` ：当前数据库上下文对象，返回是 `DbContext` 抽象类型
- `respository.DynamicDbContext`：当前数据库上下文对象，返回的是 `dynamic` 类型

如果你只是想使用 `DbContext` 的功能，直接使用 `respository.DbContext` 即可，如：

```cs
respository.DbContext.SaveChanges();
```

如果你想能够获取具体的数据库上下文类型，如 `MyDbContext`，那么使用 `respository.DynamicDbContext` 就可以获取到具体的 `MyDbContext` 类型。如：

```cs
var persons = respository.DynamicDbContext.Persons.Find(1);
var users = respository.DynamicDbContext.Users;
```

这样就可以直接操作 `MyDbContext` 定义的属性和方法了。

## 9.1.9 反馈与建议

:::note 与我们交流

给 Fur 提 [Issue](https://gitee.com/monksoul/Fur/issues/new?issue)。

:::

---

:::note 了解更多

想了解更多 `数据库上下文` 知识可查阅 [EF Core - 配置 DbContext](https://docs.microsoft.com/zh-cn/ef/core/miscellaneous/configuring-dbcontext) 章节。

:::
